You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot集成Amazon Payment Services(Pay Fort)自定义商户页面实现信用卡验证及扣款支付流程问询

Spring Boot集成Amazon Payment Services(Pay Fort)Custom Merchant Page 完整流程

我之前在Spring Boot项目里集成过Pay Fort的Custom Merchant Page,踩过不少文档里没说清的坑,现在把完整的信用卡验证+扣款流程整理出来给你参考:

前置准备

  • 先在Pay Fort后台拿到核心参数:merchant_identifier、access_code、sha_request_phrase、sha_response_phrase
  • 确定使用环境:测试环境/生产环境,两者的API地址和配置参数完全独立,别搞混

1. 前端自定义支付表单(收集卡信息)

Custom Merchant Page允许你自己搭建支付表单,直接收集用户的信用卡信息,不用跳转Pay Fort的页面:

<form id="paymentForm">
  <div>
    <label>信用卡号:</label>
    <input type="text" id="cardNumber" placeholder="4111 1111 1111 1111" required>
  </div>
  <div>
    <label>有效期 (MM/YY):</label>
    <input type="text" id="expiryDate" placeholder="05/25" required>
  </div>
  <div>
    <label>CVV:</label>
    <input type="text" id="cvv" placeholder="123" required>
  </div>
  <div>
    <label>支付金额:</label>
    <input type="text" id="amount" value="100.00" required>
  </div>
  <button type="button" onclick="submitPayment()">确认支付</button>
</form>

用JS把表单数据传给后端接口:

function submitPayment() {
  const paymentData = {
    cardNumber: document.getElementById('cardNumber').value.replace(/\s/g, ''),
    expiryDate: document.getElementById('expiryDate').value,
    cvv: document.getElementById('cvv').value,
    amount: document.getElementById('amount').value,
    currency: "SAR",
    customerEmail: "user@example.com",
    customerName: "John Doe"
  };

  fetch('/api/payfort/process-payment', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(paymentData)
  })
  .then(res => res.json())
  .then(data => {
    if (data.status === "SUCCESS") {
      alert("支付成功!");
      window.location.href = "/payment-success";
    } else {
      alert(`支付失败: ${data.message}`);
    }
  })
  .catch(err => console.error('请求异常:', err));
}

2. Spring Boot后端核心实现

2.1 配置Pay Fort参数

在application.properties里配置核心参数:

# Pay Fort配置
payfort.merchant-id=你的merchant_identifier
payfort.access-code=你的access_code
payfort.sha-request-phrase=你的sha_request_phrase
payfort.sha-response-phrase=你的sha_response_phrase
# 测试环境地址,生产环境换成正式地址
payfort.base-url=https://sbpaymentservices.payfort.com/FortAPI/paymentApi

2.2 签名工具类(重中之重)

Pay Fort所有请求/响应都需要SHA签名验证,这是官方文档最模糊的部分,一定要严格按规则来:

import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.TreeMap;

@Component
public class PayFortSignatureUtil {

    @Value("${payfort.sha-request-phrase}")
    private String shaRequestPhrase;

    @Value("${payfort.sha-response-phrase}")
    private String shaResponsePhrase;

    // 生成请求签名:参数按字母排序 → 首尾拼接sha短语 → SHA256加密转大写
    public String generateRequestSignature(Map<String, String> params) {
        TreeMap<String, String> sortedParams = new TreeMap<>(params);
        StringBuilder sb = new StringBuilder(shaRequestPhrase);
        for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
            sb.append(entry.getKey()).append("=").append(entry.getValue());
        }
        sb.append(shaRequestPhrase);
        return DigestUtils.sha256Hex(sb.toString()).toUpperCase();
    }

    // 验证响应签名,防止篡改
    public boolean validateResponseSignature(Map<String, String> responseParams) {
        TreeMap<String, String> sortedParams = new TreeMap<>(responseParams);
        sortedParams.remove("signature"); // 移除签名本身再计算
        StringBuilder sb = new StringBuilder(shaResponsePhrase);
        for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
            sb.append(entry.getKey()).append("=").append(entry.getValue());
        }
        sb.append(shaResponsePhrase);
        String calculatedSign = DigestUtils.sha256Hex(sb.toString()).toUpperCase();
        return calculatedSign.equals(responseParams.get("signature"));
    }
}

2.3 支付接口实现

创建Controller处理前端请求,调用Pay Fort API完成扣款:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/api/payfort")
public class PayFortPaymentController {

    @Value("${payfort.merchant-id}")
    private String merchantId;

    @Value("${payfort.access-code}")
    private String accessCode;

    @Value("${payfort.base-url}")
    private String payFortBaseUrl;

    private final PayFortSignatureUtil signatureUtil;
    private final RestTemplate restTemplate;

    public PayFortPaymentController(PayFortSignatureUtil signatureUtil, RestTemplate restTemplate) {
        this.signatureUtil = signatureUtil;
        this.restTemplate = restTemplate;
    }

    @PostMapping("/process-payment")
    public ResponseEntity<Map<String, Object>> processPayment(@RequestBody PaymentRequest request) {
        // 1. 构建Pay Fort请求参数
        Map<String, String> payFortParams = new HashMap<>();
        payFortParams.put("command", "PURCHASE"); // PURCHASE直接扣款,AUTHORIZE是预授权后再扣款
        payFortParams.put("merchant_identifier", merchantId);
        payFortParams.put("access_code", accessCode);
        payFortParams.put("amount", convertToMinorUnit(request.getAmount())); // 金额转最小单位,比如100.00 SAR → 10000
        payFortParams.put("currency", request.getCurrency());
        payFortParams.put("customer_email", request.getCustomerEmail());
        payFortParams.put("customer_name", request.getCustomerName());
        payFortParams.put("card_number", request.getCardNumber());
        payFortParams.put("expiry_date", request.getExpiryDate().replace("/", "")); // 转成MMYY格式,比如05/25→0525
        payFortParams.put("cvv", request.getCvv());
        payFortParams.put("language", "en");
        payFortParams.put("merchant_reference", generateOrderNo()); // 你的唯一订单号,必须唯一

        // 2. 生成签名并加入参数
        String signature = signatureUtil.generateRequestSignature(payFortParams);
        payFortParams.put("signature", signature);

        // 3. 发送请求到Pay Fort
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<Map<String, String>> httpRequest = new HttpEntity<>(payFortParams, headers);

        try {
            ResponseEntity<Map> response = restTemplate.postForEntity(payFortBaseUrl, httpRequest, Map.class);
            Map<String, Object> responseBody = response.getBody();

            // 4. 验证响应签名,防止非法请求
            if (signatureUtil.validateResponseSignature((Map<String, String>) responseBody)) {
                String responseCode = (String) responseBody.get("response_code");
                if (responseCode.startsWith("00")) {
                    // 支付成功,这里可以更新你的订单状态
                    responseBody.put("status", "SUCCESS");
                    return ResponseEntity.ok(responseBody);
                } else {
                    // 支付失败,返回错误信息
                    responseBody.put("status", "FAILED");
                    return ResponseEntity.badRequest().body(responseBody);
                }
            } else {
                return ResponseEntity.badRequest().body(Map.of("status", "FAILED", "message", "响应签名验证失败"));
            }
        } catch (Exception e) {
            return ResponseEntity.internalServerError().body(Map.of("status", "FAILED", "message", "支付请求异常: " + e.getMessage()));
        }
    }

    // 金额转最小单位(元→分,里亚尔→哈拉拉)
    private String convertToMinorUnit(String amount) {
        return String.valueOf((int) (Double.parseDouble(amount) * 100));
    }

    // 生成唯一订单号
    private String generateOrderNo() {
        return "ORDER_" + System.currentTimeMillis();
    }
}

// 支付请求DTO
class PaymentRequest {
    private String cardNumber;
    private String expiryDate;
    private String cvv;
    private String amount;
    private String currency;
    private String customerEmail;
    private String customerName;

    // Getters & Setters
    public String getCardNumber() { return cardNumber; }
    public void setCardNumber(String cardNumber) { this.cardNumber = cardNumber; }
    public String getExpiryDate() { return expiryDate; }
    public void setExpiryDate(String expiryDate) { this.expiryDate = expiryDate; }
    public String getCvv() { return cvv; }
    public void setCvv(String cvv) { this.cvv = cvv; }
    public String getAmount() { return amount; }
    public void setAmount(String amount) { this.amount = amount; }
    public String getCurrency() { return currency; }
    public void setCurrency(String currency) { this.currency = currency; }
    public String getCustomerEmail() { return customerEmail; }
    public void setCustomerEmail(String customerEmail) { this.customerEmail = customerEmail; }
    public String getCustomerName() { return customerName; }
    public void setCustomerName(String customerName) { this.customerName = customerName; }
}

3. 关键注意事项

  • 金额格式:必须转成最小单位,比如10.5欧元要传1050,否则会直接报错
  • 签名规则:参数必须按字母顺序排序,拼接sha短语的位置不能错(首尾都要加),加密后必须转大写
  • 卡信息格式:有效期要去掉斜杠,卡号不能有空格
  • 异步回调:如果需要Pay Fort主动通知支付结果,要在后台配置回调URL,并且在后端实现回调接口(用上面的签名工具类验证响应)
  • 测试用卡:测试环境可以用4111 1111 1111 1111,有效期05/25,CVV123来模拟成功支付

内容的提问来源于stack exchange,提问作者Abdalrhman Alkraien

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 15:37:47