AllinpayCnp
通联支付 CNP 跨境信用卡收单 Ruby SDK。
安装
Gemfile
gem 'allinpay_cnp', '~> 0.1.6'
本地安装
cd allinpay_cnp
gem build allinpay_cnp.gemspec
gem install allinpay_cnp-0.1.6.gem
配置
Rails 项目
创建 config/initializers/allinpay_cnp.rb:
AllinpayCnp.configure do |config|
config.merchant_id = Rails.application.credentials.dig(:allinpay, :merchant_id)
config.private_key = Rails.application.credentials.dig(:allinpay, :private_key)
config.public_key = Rails.application.credentials.dig(:allinpay, :public_key)
config.inst_no = Rails.application.credentials.dig(:allinpay, :inst_no)
config.environment = Rails.env.production? ? :production : :test
config.timeout = 30
config.logger = Rails.logger
end
普通 Ruby 项目
require 'allinpay_cnp'
AllinpayCnp.configure do |config|
config.merchant_id = '086310030670001'
config.private_key = File.read('private_key.pem')
config.public_key = File.read('public_key.pem')
config.inst_no = '00000001' # 机构模式下必填,直连商户模式不填
config.environment = :test # :test 或 :production
config.timeout = 30
config.logger = Logger.new($stdout)
end
配置项
| 配置项 | 类型 | 必填 | 说明 |
|---|---|---|---|
merchant_id |
String | 条件必填 | 商户号。若每次调用时都传入 merchant_no,可不在全局配置 |
private_key |
String | 条件必填 | 商户私钥 (PEM 格式),用于请求签名。若每次调用时都传入 private_key,可不在全局配置 |
public_key |
String | 否 | 系统公钥 (PEM 格式),用于验证回调/应答签名。亦可在调用时通过 public_key / verify_callback(public_key:) 传入 |
inst_no |
String | 否 | 机构号,机构模式下必填,直连商户模式留空 |
environment |
Symbol | 否 | :test (默认) 或 :production |
timeout |
Integer | 否 | 请求超时时间,默认 30 秒 |
logger |
Logger | 否 | 日志对象 |
多商户模式下可以不在全局配置
merchant_id/private_key,而是在每次调用时按商户传入。详见 多商户模式。
密钥说明
| 密钥 | 用途 | 对应配置 |
|---|---|---|
| 商户私钥 | 请求时计算签名 | config.private_key |
| 系统公钥 | 接收应答/异步通知时验签 | config.public_key |
| 商户公钥 | 上传至通联后台,通联用来验证你的请求签名 | 不需要配置到代码中 |
使用方法
获取客户端
client = AllinpayCnp.client
统一收银台支付 (Unified Pay)
跳转到通联支付页面完成支付:
response = client.unified_pay(
access_order_id: "ORDER_#{Time.now.to_i}",
amount: '100.00',
currency: 'HKD',
urls: {
notify_url: 'https://your-domain.com/webhooks/allinpay',
return_url: 'https://your-domain.com/payments/complete'
},
email: 'customer@example.com',
language: 'zh-hant', # 可选: zh-hant, zh-hans, en
product_info: [ # 商品信息,可选,会以 JSON 字符串形式发送
{ sku: 'SKU001', productName: 'Test Product', price: '100.00', quantity: '1' }
],
shipping: {
first_name: 'Peter',
last_name: 'Zhang',
address1: '123 Test Street',
city: 'Hong Kong',
country: 'HK',
zip_code: '000000',
phone: '12345678'
},
billing: {
first_name: 'Peter',
last_name: 'Zhang',
address1: '123 Test Street',
city: 'Hong Kong',
country: 'HK',
zip_code: '000000',
phone: '12345678'
}
)
if response.success?
redirect_to response.payment_url
else
puts "Error: #{response.result_desc}"
end
查询订单 (Query)
response = client.query(ori_access_order_id: 'ORIGINAL_ORDER_123')
if response.success?
puts "状态: #{response.status}" # SUCCESS, FAIL, PROCESSING
puts "金额: #{response.amount}"
puts "币种: #{response.currency}"
puts "卡号: #{response.card_no}" # 脱敏卡号
puts "卡组织: #{response.card_orgn}" # VISA, MASTERCARD 等
end
退款 (Refund)
response = client.refund(
ori_access_order_id: 'ORIGINAL_ORDER_123',
refund_amount: '50.00'
)
if response.success?
puts '退款成功'
else
puts "退款失败: #{response.result_desc}"
end
验证回调签名
# Rails Controller
class WebhooksController < ApplicationController
skip_before_action :verify_authenticity_token, only: [:allinpay]
def allinpay
client = AllinpayCnp.client
unless client.verify_callback(params.to_unsafe_h)
render plain: 'FAIL' and return
end
if params[:resultCode] == '0000'
payment = Payment.find_by(gateway_order_id: params[:accessOrderId])
payment&.mark_as_paid!(
transaction_ref: params[:orderId],
card_no: params[:cardNo],
card_orgn: params[:cardOrgn]
)
end
render plain: 'SUCCESS' # 必须返回 SUCCESS
end
end
多商户模式 (Per-call credentials)
如果你的应用需要为多个商户代发请求 (商户模式 / 非机构模式),每个商户拥有独立的密钥对。可在调用时传入 private_key / public_key / merchant_no,会覆盖全局配置:
response = client.unified_pay(
access_order_id: "ORDER_#{Time.now.to_i}",
amount: '100.00',
currency: 'HKD',
urls: { notify_url: '...', return_url: '...' },
merchant_no: merchant.allinpay_id,
private_key: merchant.allinpay_private_key,
public_key: merchant.allinpay_public_key
)
# query / refund 同样支持
client.query(ori_access_order_id: 'ORDER_123',
merchant_no: merchant.allinpay_id,
private_key: merchant.allinpay_private_key,
public_key: merchant.allinpay_public_key)
# 回调验签时使用对应商户的 public_key
client.verify_callback(params, public_key: merchant.allinpay_public_key)
未传入时会回退到 AllinpayCnp.configure 中的全局配置,因此单商户用法完全向后兼容。
Response 对象
所有 API 方法都返回 Response 对象:
response.success? # 是否成功 (resultCode == '0000')
response.failure? # 是否失败
response.result_code # 返回码
response.result_desc # 返回描述
response.payment_url # 支付页面 URL (unified_pay)
response.access_order_id # 商户订单号
response.order_id # CNP 系统订单号
response.status # 订单状态 (query)
response.amount # 金额
response.currency # 币种
response.card_no # 脱敏卡号
response.card_orgn # 卡组织
response.body # 原始响应 Hash
response['customField'] # 获取任意字段
API 地址
运行测试
bundle install
bundle exec rspec
目录结构
lib/
├── allinpay_cnp.rb # 主入口
└── allinpay_cnp/
├── version.rb # 版本号
├── config.rb # 配置类
├── signature.rb # RSA2 签名
├── request.rb # HTTP 请求
├── response.rb # 响应封装
└── client.rb # API 客户端