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 地址

环境 Unified Pay QuickPay
测试 https://cnp-test.allinpay.com/gateway/cnp/unifiedPay https://cnp-test.allinpay.com/gateway/cnp/quickpay
生产 https://cnp.allinpay.com/gateway/cnp/unifiedPay https://cnp.allinpay.com/gateway/cnp/quickpay

运行测试

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 客户端