Paymill subscription in PHP 为何创建两次付款?
结论
重复扣款是因为代码先调用了 Services_Paymill_Transactions::create()。
创建 transaction 时会立即扣款。随后创建 subscription,Paymill 又会按照订阅规则发起首次扣款,于是同一次操作产生了两笔付款。
获取 payment_id 不需要先创建 transaction。可以直接使用前端生成的 paymillToken 创建 payment object,再将 payment ID 传给 subscription。
正确流程
- 前端通过 Paymill Bridge 获取一次性
paymillToken。 - 使用该 token 调用 Payments API,创建 payment object。
- 从返回结果中获取 payment ID 和 client ID。
- 使用
payment、client和offer创建 subscription。 - 除非需要单独进行一次即时扣款,否则不要额外创建 transaction。
创建 payment object 只是保存后续扣款所需的支付方式,并不会创建 transaction,也不会单独扣款。
代码示例
下面继续使用问题中的旧版 Services_Paymill_* PHP 接口。SDK 版本不同,返回字段的结构也可能不同,请以项目实际安装的版本为准。
<?php
$token = $_POST['paymillToken'] ?? null;
if (!$token) {
throw new RuntimeException('Missing paymillToken');
}
require 'Services/Paymill/Payments.php';
require 'Services/Paymill/Subscriptions.php';
$paymentsObject = new Services_Paymill_Payments(
PAYMILL_API_KEY,
PAYMILL_API_HOST
);
// 这里只创建支付方式,不创建 transaction,因此不会单独立即扣款。
$payment = $paymentsObject->create([
'token' => $token,
]);
$paymentId = $payment['id'];
// 不同版本的 SDK 可能返回 client ID 字符串,也可能返回 client 对象。
$clientId = is_array($payment['client'])
? $payment['client']['id']
: $payment['client'];
$subscriptionsObject = new Services_Paymill_Subscriptions(
PAYMILL_API_KEY,
PAYMILL_API_HOST
);
$subscription = $subscriptionsObject->create([
'client' => $clientId,
'offer' => 'offer_9cdffb501f565bf827a8',
'payment' => $paymentId,
]);
print_r($subscription);
如果当前 SDK 要求显式创建 client,可以先创建 client,再将其 ID 传给 payment:
<?php
require 'Services/Paymill/Clients.php';
require 'Services/Paymill/Payments.php';
require 'Services/Paymill/Subscriptions.php';
$clientsObject = new Services_Paymill_Clients(
PAYMILL_API_KEY,
PAYMILL_API_HOST
);
$client = $clientsObject->create([
'email' => $userEmail,
'description' => 'User ID# ' . $userIdMain,
]);
$paymentsObject = new Services_Paymill_Payments(
PAYMILL_API_KEY,
PAYMILL_API_HOST
);
$payment = $paymentsObject->create([
'token' => $token,
'client' => $client['id'],
]);
$subscriptionsObject = new Services_Paymill_Subscriptions(
PAYMILL_API_KEY,
PAYMILL_API_HOST
);
$subscription = $subscriptionsObject->create([
'client' => $client['id'],
'offer' => 'offer_9cdffb501f565bf827a8',
'payment' => $payment['id'],
]);
为什么原代码会重复扣款
原代码执行了两次收费操作。
第一次是创建 transaction:
$transaction = $transactionsObject->create($params);
这段代码使用 token 创建并执行了一笔即时交易,金额为 49900,币种为 SEK。
随后又创建了 subscription:
$subscription = $subscriptionsObject->create($params);
根据 offer 配置和订阅开始时间,subscription 可能立即产生首笔订阅扣款。因此,同一次请求中既有手动创建的 transaction,也有订阅的首次扣款。
$transaction['payment']['id'] 是 transaction 返回结果中包含的 payment ID,但这不代表 payment 必须通过 transaction 获得。Payments API 可以直接使用 token 创建 payment object。
注意事项
paymillToken通常只能使用一次,不要重复提交。amount使用最小货币单位。49900对应的实际金额取决于该币种的指数规则。对于 SEK,通常可理解为499.00 SEK,但仍应以当时 Paymill 的币种处理规则为准。- 如果 offer 已经定义了订阅金额和周期,就不需要再通过 transaction 收取同一笔首期费用。
- 只有在业务确实需要立即收取开通费,并另行开始周期订阅时,才应同时创建 transaction 和 subscription。此时,两笔费用必须对应不同的收费项目。
- 提交操作应增加幂等或本地状态检查,避免用户刷新页面、重复提交表单或网络重试时创建多个 subscription。
- Paymill 及其旧版 PHP SDK 已属于历史接口。字段结构和具体行为应以项目实际使用的 SDK 版本及当时的 API 文档为准。
备注:内容仅供参考。
