65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Mastercard MIGS 服务和 ASP.NET Core 捕获付款

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2021 年 2 月 3 日

CPOL

11分钟阅读

viewsIcon

11881

Mastercard MIGS 付款服务以及如何从客户处捕获付款

概述

大家好!今天,我们将探讨 Mastercard MIGS 支付服务,并学习如何通过 C# 和 ASP.NET Core 提供的 API 从客户那里捕获付款。

值得一提的是,我们将创建的支付组件可以从任何项目(不仅仅是 ASP.NET core 项目)中使用。此外,将我们的测试代码移植到 MVC、WebForms 甚至控制台/桌面应用程序也非常容易。

完整源代码

完整代码及其测试 Web 应用程序可在以下链接中找到。代码有完整的文档,易于理解。

该软件包也可在 Nuget 上找到,链接如下:

引言

Mastercard 提供一项名为 MIGS(Mastercard Internet Gateway Service)的服务,商户可以使用该服务向客户收款以及退款和监控报告。该服务通过一个名为 VPC(Virtual Payment Client)的 API 暴露其功能,可以使用商户从其收单行获得的某些凭据轻松访问。

换句话说,VPC(Virtual Payment Client)充当一个接口,在您的在线商店和 Mastercard 支付服务器之间提供安全的通信方式,从而促进支付处理。

为此,作为商户,您必须通过您的银行注册电子支付服务,银行将为您提供两组凭据,用于与 VPC 通信,即测试凭据和生产凭据。

安全凭据

您将从银行收到的每组安全凭据(测试/生产)包含五个部分:

  • 商户 ID:您可以将其视为用户名。
  • 访问码:您可以将其视为密码。
  • 安全哈希密钥:可用于完整性验证(我们稍后会介绍)。此代码必须妥善保管。
  • AMA 用户:稍后将在交易查询中介绍。
  • AMA 密码:稍后将在交易查询中介绍。

集成模式

要从客户那里收款,您需要使用以下两种模式之一:

  • 服务器托管模式
  • 商户托管模式

服务器托管模式 / 3 方

服务器托管模式,也称为银行托管和 3 方模式,在这种模式下,您不直接处理客户卡信息。相反,您会将用户重定向到一个由 Mastercard 托管的安全页面,用户可以在该页面上输入他们的卡信息,然后您等待页面将响应返回给您。

此模式的优缺点

  • 优点
    • 您完全无需负责保护用户卡信息。
  • 缺点
    • 用户必须在场。
    • 不能用于离线或邮购/电话订单。
    • 如果页面未返回给您(例如,用户关闭了浏览器或出现网络问题),您必须检查待处理的交易。

商户托管模式 / 2 方

商户托管模式,也称为 2 方模式,在这种模式下,您直接处理客户卡信息。您向用户索要卡信息或从数据库中检索信息,然后通过 Web 请求将所有必需信息发送到 VPC,VPC 会立即回复相关信息。

2 方模式的优缺点

  • 优点
    • 无需等待。您会立即收到回复。
    • 用户可能不在场。
    • 可用于邮购/电话订单。
  • 缺点
    • 您必须处理信用卡页面和数据库中存储的数据(例如)的安全性。

终结点

要开始使用某种模式,您需要使用下表中提供的相关端点:

模型 端点
服务器托管 vpcpay
商户托管 vpcdps

支付交易

现在,让我们开始工作。要向服务器发出支付请求,您应包含以下参数:

参数 描述
Common vpc_Version API 版本。当前支持的版本是“1”。
vpc_Command 命令名称,支付请求为“pay”。
身份验证 vpc_Merchant 您的商户 ID。
vpc_AccessCode 您的访问码。
订单详情 vpc_MerchTxnRef 您的唯一交易参考号。
vpc_OrderInfo 您的订单 ID。
vpc_Amount 金额,以最小货币单位表示为整数。例如,如果交易金额为 49.95 美元,则金额值应为 4995。

交易参考号与订单信息

现在您可能会问,交易参考号和订单信息有什么区别?是什么使得交易参考号被强烈建议为唯一的?

答案是,您是否曾经想过,在服务器托管的交易中,如果用户在成功支付后关闭 Mastercard 页面而没有返回您的网站会发生什么?答案是,如果没有唯一的交易参考号,您将无法访问用户的支付交易。

使用唯一的交易参考号可让您识别每个支付请求并将其与用户/订单关联。如果您没有收到支付请求的响应,您可以使用该唯一参考号稍后使用 QueryDR 命令(稍后将介绍)查询支付状态。

您也可以在 vpc_OrderInfo 字段中传递订单 ID,但请记住,每个订单可能有多个支付请求,但每个支付请求只能关联到一个订单。

服务器托管支付交易

如果您将使用服务器托管集成模式,您将需要将您的调用指向正确的 API 端点,即服务器托管模式的 vpcpay,并除了上述参数外,还需要提供另外两个参数。其中一个参数,您可能猜到,是返回 URL(即回调 URL)。

参数 描述
vpc_ReturnURL 交易完成后 Mastercard 网关将导航到的 URL。在此 URL 中,您可以定义一个处理支付交易结果的句柄。您可以定义一个感谢页面、一个收据页面或类似的页面。请记住,并非所有交易都成功。此 URL 必须
  1. 绝对寻址
  2. 符合 SSL 标准。
vpc_Locale Mastercard 服务器页面的 ISO 两字母语言代码。例如:en(英语)、ar(阿拉伯语)和es(西班牙语)。

现在的问题是:我们如何将这些参数发送到 VPC API?我们是将其包含在请求正文中吗?我们会将其编码为 JSON 吗?答案是否定的!我们只是将其包含在查询字符串中。由于我们使用的是服务器托管模式,我们将准备一个包含这些参数的 URL(在查询字符串中),然后我们只需导航到此 URL,然后等待它导航回我们的返回 URL(vpc_ReturnURL)。

值得一提的是,您可以定义自己的自定义参数,只要它们不以“vpc_”开头即可。

商户托管支付交易

另一方面,商户托管交易使用 vpcdps 端点。它使用您很容易猜到的另外 3 个参数:

参数 描述
vpc_Card 自定义卡号
vpc_CardExp 卡片有效期(以 yyMM 格式表示)。例如,2021 年 1 月表示为 2101。
vpc_CardSecurityCode 卡片安全码

现在让我们将这些部分组合起来。

响应参数

如果我们使用的是服务器托管模式,我们可能会在回调 URL 的查询参数中收到响应。另一方面,如果我们使用的是商户托管模式,我们可能会在 Web 响应的正文的查询字符串中收到响应。这些参数包括:

参数 描述
vpc_MerchTxnRef 我们的唯一交易参考号
vpc_OrderInfo 我们的订单号
vpc_TxnResponseCode 交易响应代码。“0”表示交易成功。
vpc_Message 指示交易可能遇到的任何错误消息。
vpc_ReceiptNo 交易唯一标识符。也称为参考检索号(RRN)。
vpc_AuthorizeId 银行颁发的用于批准或拒绝交易的标识代码。
vpc_BatchNo 银行提供的日期,指示此交易何时结算。
vpc_CardType 卡片类型。例如,“MC”表示 Mastercard 卡。

请求和响应示例

这是一个请求 URL 的示例:

https://migs.mastercard.com.au/vpcpay?vpc_AccessCode=77426638&vpc_Amount=10025&
vpc_Command=pay&vpc_Locale=en&vpc_MerchTxnRef=TX-1&vpc_Merchant=TESTEGPTEST&
vpc_OrderInfo=100&vpc_ReturnURL=https://:44376/Home/PaymentCallback&vpc_Version=1

在这里,您可以看到端点是“vpcpay”,这表示服务器托管模式(即,我们将用户重定向到此链接。我们还可以看到金额是 10025,这意味着 100.25 美元(或我们正在运营的任何货币)。我们还可以看到我们指定的返回 URL 是“https://:44376/Home/PaymentCallback'。当用户完成交易时,他将自动导航到此 URL。

当用户完成交易时,他将被导航到

https://:44376/Home/PaymentCallback&vpc_Amount=121300&vpc_AuthorizeId=586587&
vpc_BatchNo=20210129&vpc_CSCResultCode=M&vpc_Card=MC&vpc_Command=pay&
vpc_MerchTxnRef=TX-1&vpc_Merchant=TESTEGPTEST&vpc_Message=Approved&
vpc_OrderInfo=O-1&vpc_ReceiptNo=103006586587&vpc_TxnResponseCode=0&vpc_Version=1

无需更多解释。参数易于理解和遵循。

完整性检查

您是否曾想过,如果用户模拟支付过程并仅使用任何伪造值调用您网站的上述 URL 会怎么样?您仍然会将其视为有效交易吗?您如何区分真实和伪造的响应?答案是完整性检查。

安全凭据中我们尚未介绍的最后一个三分之一是安全哈希密钥。此代码用于防止持卡人在通过持卡人浏览器传递交易请求和响应时修改它们。这就是我们所说的完整性检查。

完整性检查的工作方式是使用哈希密钥代码作为密钥来哈希我们所有的参数,并将生成的哈希与我们的服务器调用一起发送,服务器将验证哈希并在有效时继续。相反,服务器使用相同的哈希密钥代码哈希所有响应参数,并将响应与生成的哈希一起返回给我们的应用程序,我们可以通过重新生成哈希并将其与返回的值进行比较来验证它。

在此过程中应注意三点:

  1. VPC 要求在哈希之前按 ASCII 顺序对哈希参数进行排序,例如,大写“E”在小写“a”之前。
  2. VPC 要求使用 HMAC SHA-256 或 MD5 哈希算法。
  3. 必须妥善保管哈希密钥,绝不应泄露。

因此,为了使此功能正常工作,我们在请求中会包含两个额外的参数:

参数 描述
vpc_SecureHash 生成的哈希
vpc_SecureHashType 哈希机制,即“SHA256”或“MD5”。
请注意不要包含您的原始密钥哈希。

让我们看看实际应用。

SHA-256 哈希

对于 SHA-256 哈希,您可以使用以下过程进行请求:

另一方面,这是 SHA-256 的响应完整性检查机制。

MD5 哈希

对于 MD5 请求哈希,请使用以下过程:

对于响应检查,请使用以下过程:

交易查询

之前,我们提出了一个问题,即在服务器托管交易中,如果用户关闭了浏览器(例如)而没有返回您的网站会发生什么?答案在于您的唯一商户交易号。您可以使用此号码在查询 API 中查询具有此号码的交易。这就是为什么强烈建议此号码是唯一的。

查询命令基于商户托管模式。这意味着不会有导航,您将创建请求并立即读取其响应。这也意味着它将使用 vpcdps 端点。

要点是,交易查询命令是高级商户管理(AMA)命令的一部分。这些高级命令使用额外的安全凭据,称为 AMA 用户名和密码。这些凭据必须作为查询参数包含在请求中。

对于交易查询请求,我们将使用以下参数:

参数 描述
Common vpc_Version API 版本。当前支持的版本是“1”。
vpc_Command 命令名称,为“queryDR”。
身份验证 vpc_Merchant 您的商户 ID。
vpc_AccessCode 您的访问码。
vpc_User AMA 用户名。
vpc_Password AMA 密码。
请求详情 vpc_MerchTxnRef 您的唯一交易参考号。

对于响应,我们将有以下参数:

参数 描述
vpc_DRExists 如果找到请求的交易参考号,则返回“Y”。
vpc_FoundMultipleDRs 如果存在多个具有请求的商户交易参考号的交易,则返回“Y”。
vpc_Amount 如果找到一笔交易,我们将在此处显示其金额。
vpc_BatchNo 如果找到一笔交易,我们将在此处显示其批号。
vpc_TransactionNo 如果找到一笔交易,我们将在此处显示其唯一交易号。

测试环境

Credentials

这是一组可用于支付过程的测试凭据。通常,您将从您的收单行收到您的测试凭据。您也可以在互联网上搜索并获得一些测试凭据,如下所示。

项目
商户 ID TESTEGPTEST
访问码 77426638
安全哈希 7E5C2F4D270600C61F5386167ECB8DA6

测试卡

这里有一组您可以使用的测试卡:

项目 卡 #1 卡 #2
类型 万事达卡 维萨卡
数字 5123456789012346 4987654321098769
有效期 05/21 05/21
安全码 100 100

代码一瞥

在本节中,我们将快速浏览代码。

让我们从查询参数开始。为了让我们的代码动态生成参数,并区分参数字段和其他字段,我们创建了一个属性,用于装饰仅参数字段。

  [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, 
                  AllowMultiple = false, Inherited = true)]
  public class QueryParamAttribute : Attribute
  {
    /// <summary>
    /// Target parameter name.
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// Is parameter required. Indicates whether to include an empty string 
    /// if parameter value is null/empty.  Default is True.
    /// </summary>
    public bool IsRequired { get; set; }
  }

接下来,我们开始定义我们的类层次结构和参数属性:

  public abstract class VpcCommand
  {
    /// <summary>
    /// Command name.
    /// </summary>
    [QueryParam(Name = "vpc_Command", IsRequired = true)]
    public abstract string Command { get; }

我们使用反射来获取参数查询属性及其对应的值。

private static IEnumerable<MemberInfo> GetObjectQueryMembers(object targetObject)
{
  IEnumerable<MemberInfo> queryMembers;

  // Loads for instance properties
  queryMembers = targetObject.GetType().GetProperties();
  // Loads for instance fields
  queryMembers = queryMembers.Concat(targetObject.GetType().GetFields());

  // Checks QueryParamAttribute existence
  queryMembers = queryMembers.Where(a => a.GetCustomAttributes<QueryParamAttribute>().Any());
  return queryMembers;
}

我们使用简单的代码生成和连接查询字符串。

    public static string CreateQueryString(IEnumerable<QueryParameter> parameters)
    {
      string queryStr = string.Empty;
      foreach (var param in parameters)
      {
        queryStr += string.Format("{0}={1}&", param.Name, param.Value);
      }

      queryStr = queryStr.TrimEnd('&');
      return queryStr;
    }

这是我们的安全哈希生成代码:

public virtual string SHA256Hash(string hashSecret, IEnumerable<QueryParameter> queryParams)
{
  queryParams = queryParams.OrderBy(a => a.Name, StringComparer.Ordinal);
  string queryStr = QueryManager.CreateQueryString(queryParams);
  return Sha256(queryStr, hashSecret);
}

public virtual string MD5Hash(string hashSecret, IEnumerable<QueryParameter> queryParams)
{
  queryParams = queryParams.OrderBy(a => a.Name, StringComparer.Ordinal);

  string str = hashSecret + string.Join("", queryParams.Select(a => a.Value));

  return MD5(str);
}

执行商户托管命令的代码相当简单:

public virtual string ExecuteCommandRaw(VpcCommand cmd)
{
  string reqUrl = ComputeCommand(cmd);

  WebRequest req = HttpWebRequest.Create(reqUrl);

  req.Method = "POST";

  using (var stm = req.GetResponse().GetResponseStream())
  using (var stmReader = new StreamReader(stm))
  {
    return stmReader.ReadToEnd();
  }

历史

  • 2021 年 2 月 3 日:初始版本
© . All rights reserved.