JAVA实现PayPay支付对接

一.介绍

PayPay 于2018年推出,已成为日本领先的移动支付应用,拥有超过6200万的用户群。该应用程序的成功归功于其战略营销活动、用户友好的支付体验、多样化的服务以及在线和离线平台的无缝集成。自成立以来,PayPay 有效地引导了当地消费者从传统的现金交易转向数字支付的便利,使其成为日本个人日常生活中不可或缺的工具。

PayPay 与银行账户、信用卡和借记卡集成,简化了 PayPay 钱包的充值流程。无论是在线购物还是店内购物,买家都可以通过扫描二维码、条形码或输入账户信息轻松完成交易。随着全日本的广泛接受,PayPay 的支付率超过45%。

二.注册PayPay开发者账户与安装应用

开发者平台:https://developer.paypay.ne.jp/

开发环境中 PayPay 应用程序使用介绍(页面内也有相应下载方式):https://integration.paypay.ne.jp/hc/ja/articles/4414061901199-%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83%E3%81%A7PayPay%E3%82%A2%E3%83%97%E3%83%AA%E3%82%92%E5%88%A9%E7%94%A8%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99%E3%81%8B

百度网盘下载:链接: https://pan.baidu.com/s/1daCBhbqGraQHNQf6pSZ10Q?pwd=frnu 提取码: frnu

账户注册成功,工作台是这样。Merchant ID、Key Id、Key Secret这三个参数用于身份验证。Test User中会有三个测试账号用于登录PayPay。

在这里插入图片描述

基础参数已经有了,接下来开始对接。

接口文档:https://www.paypay.ne.jp/opa/doc/v1.0/webcashier#tag/Payment/operation/getRefundDetails

Webhook 通知源服务器的 IP 地址:https://integration.paypay.ne.jp/hc/ja/articles/4414062832143-Webhook%E9%80%9A%E7%9F%A5%E5%85%83%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%81%AEIP%E3%82%A2%E3%83%89%E3%83%AC%E3%82%B9%E3%82%92%E6%95%99%E3%81%88%E3%81%A6%E4%B8%8B%E3%81%95%E3%81%84

三.对接网络支付

了解支付流程
在这里插入图片描述

1.引入依赖

<dependency>
    <groupId>jp.ne.paypay</groupId>
    <artifactId>paypayopa</artifactId>
    <version>1.0.8</version>
</dependency>

2.身份验证

private static final String YOUR_API_KEY = "";
private static final String YOUR_API_SECRET = "";
private static final String YOUR_MERCHANT_KEY = "";
private static final ApiClient apiClient;
static {
    try {
        apiClient = new Configuration().getDefaultApiClient();
        apiClient.setProductionMode(false);
        apiClient.setApiKey(YOUR_API_KEY);
        apiClient.setApiSecretKey(YOUR_API_SECRET);
        apiClient.setAssumeMerchant(YOUR_MERCHANT_KEY);
    } catch (ApiException e) {
        throw new RuntimeException(e);
    }
}

3.支付与回调

(1)创建支付二维码
	/**
     * 创建支付二维码
     *
     * @param merchantPaymentId 商户付款编号
     * @param payAmount 支付金额
     * @param orderDescription 支付描述
     * @return 创建信息
     */
    public static QRCodeResponse getQrCode(String merchantPaymentId, Integer payAmount, String orderDescription) throws ApiException {
        QRCode qrCode = new QRCode();
        qrCode.setAmount(new MoneyAmount().amount(payAmount).currency(MoneyAmount.CurrencyEnum.JPY));
        qrCode.setMerchantPaymentId(merchantPaymentId);
        qrCode.setCodeType("ORDER_QR");
        qrCode.setOrderDescription(orderDescription);
        qrCode.isAuthorization(false);
        PaymentApi apiInstance = new PaymentApi(apiClient);
        QRCodeDetails response = apiInstance.createQRCode(qrCode);
        QRCodeResponse responseData = null;
        if (PAY_SUCCESS.equals(response.getResultInfo().getCode())) {
            responseData = response.getData();
        } else {
            log.info("创建二维码,Error code : {}", response.getResultInfo().getCode());
            throw new RuntimeException((response.getResultInfo().getMessage()));
        }
        return responseData;
    }

测试验证

private static final String PRODUCT_PAY_PAY = "PayOr";

@Test
public void createPaymentTest() {
    String orderNo = IdUtil.getSnowflake(0, 0).nextIdStr();
    System.out.println("随机生成订单号" + orderNo);
    QRCodeResponse QRCodeResponse = new QRCodeResponse();
    try {
        String merchantPaymentId = PRODUCT_PAY_PAY + orderNo;
        String orderDescription = "测试";
        Integer paAmount = 1;
        QRCodeResponse = getQrCode(merchantPaymentId, paAmount, orderDescription);
    } catch (ApiException e) {
        System.out.println("Error code : " + e.getMessage());
        throw new RuntimeException(e);
    }
    System.out.println(QRCodeResponse);
    System.out.println(QRCodeResponse.getUrl());
}

测试结果

随机生成订单号1858850799262629888
20:32:21.213 [main] INFO org.hibernate.validator.internal.util.Version -- HV000001: Hibernate Validator 8.0.1.Final

API: POST https://stg-api.sandbox.paypay.ne.jp/v2/codes
QRCodeResponse {
    codeId: 04-ynVsCNYaue3wMdxP
    url: https://qr-stg.sandbox.paypay.ne.jp/28180104ynVsCNYaue3wMdxP
    deep Link: paypay://payment?link_key=https%3A%2F%2Fqr-stg.sandbox.paypay.ne.jp%2F28180104ynVsCNYaue3wMdxP
    expiryDate: 1732019841
    merchantPaymentId: PayOr1858850799262629888
    amount: class MoneyAmount {
        amount: 1
        currency: JPY
    }
    orderDescription: 测试
    orderItems: null
    metadata: null
    codeType: ORDER_QR
    storeInfo: null
    storeId: null
    terminalId: null
    requestedAt: 1732019541
    redirectUrl: null
    redirectType: null
    isAuthorization: false
    authorizationExpiry: null
}
Url :https://qr-stg.sandbox.paypay.ne.jp/28180104ynVsCNYaue3wMdxP

url及跳转连接。在浏览器中打开如下图所示。

在这里插入图片描述

也可复制粘贴在手机浏览器打开。

在这里插入图片描述

完成付款。paypay会将回调返回到服务端。

(2)支付回调

PayPay 可以发送 webhook 事件,在您的帐户发生事件时通知您的应用程序。为了能够接收通知,客户端需要设置一个 webhook url,使用 POST 方法将数据发送到客户端。所有通知数据都将有一个 notification_type 字段,客户端服务可以使用该字段来确定发生了什么事件。当您的应用程序通过 webhook 收到通知时,它应该使用 HTTP 200 OK 状态代码进行响应。虽然不是必需的,但建议使用带有简短文本消(如“OK”)的响应主体。

回调返回数据格式。

{
  "notification_type": "Transaction",
  "merchant_id": "",
  "store_id": "",
  "pos_id": "",
  "order_id": "",
  "merchant_order_id": "",
  "authorized_at": "",
  "expires_at": "",
  "paid_at": null,
  "order_amount": 1,
  "reauth_request_id": "",
  "state": "COMPLETED"
}

这里使用实体类接收回调数据。

@Data
public class PayPayNotification {
    
    @JsonProperty("notification_type")
    private String notificationType;
    
    @JsonProperty("store_id")
    private String storeId;

    @JsonProperty("paid_at")
    private DateTime paidAt;

    @JsonProperty("expires_at")
    private String expiresAt;

    @JsonProperty("merchant_order_id")
    private String merchantOrderId;

    @JsonProperty("pos_id")
    private String posId;

    @JsonProperty("order_amount")
    private String orderAmount;

    @JsonProperty("merchant_id")
    private String merchantId;

    @JsonProperty("state")
    private String state;

    @JsonProperty("order_id")
    private String orderId;

    @JsonProperty("authorized_at")
    private String authorizedAt;
}

在服务端,编写处理支付结果的方法。这里我只处理了 “state”: "COMPLETED"的事件。其余还有CREATED、AUTHORIZED、REAUTHORIZING、REFUNDED、FAILED、CANCELED、EXPIRED。可以根据自己需求分别处理。

特殊处理:创建支付二维码测试方法中,在随机生成的orderNo前,拼接了一个前缀 PayOr。这个特殊处理是为了满足,当需求中出现不同的支付场景时,回调需要分开处理的情况。因为PayPay不像微信支付一样可以指定回调地址。经过前缀处理之后,我们可以在回调中,根据merchantOrderId前缀的不同,分别处理。


private static final String PRODUCT_PAY_PAY = "PayOr";

@SaIgnore
@PostMapping(value = "/payNotify")
public String payNotify(@RequestBody PayPayNotification payPayNotification) {
    log.info("进入支付回调---{}", payPayNotification);
    String merchantOrderId = payPayNotification.getMerchantOrderId();
    String prefix = merchantOrderId.substring(0, 5);
    String orderNo = merchantOrderId.substring(5);
    String state = payPayNotification.getState();
    if (COMPLETED.getValue().equals(state) && PRODUCT_PAY_PAY.equals(prefix)) {
         //因申请退款需要获得PayPay提供的付款交易ID
         //所以在回调中要把 PayPay提供的付款交易ID保存
         System.out.println("付款成功,paypay付款交易ID:" + payPayNotification.getOrderId());
    }
    return "OK";
} 

服务端回调处理写好之后,要在PayPay工作台配置回调地址。PAYMENT_STATUS_WEBHOOK 为付款状态的推送。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

配置完成之后,支付刚刚生成的订单,服务端会收到回调。

进入支付回调---PayPayNotification(notificationType=Transaction, storeId=null, paidAt=2024-11-19 21:34:37, expiresAt=null, merchantOrderId=PayOr1858850799262629888, posId=, orderAmount=1, merchantId=848069133234880512, state=COMPLETED, orderId=04577353467534417920, authorizedAt=null)
付款成功,paypay付款交易ID:04577353467534417920

4.申请退款

    /**
     * 申请退款
     *
     * @param merchantRefundId 	商户提供的唯一退款交易ID
     * @param paypayPaymentId PayPay 提供的付款交易 ID
     * @param refundAmount 待退款交易金额
     * @param refundReason  退款原因
     * @return 退款信息
     */
    public static Refund refund(String merchantRefundId, String paypayPaymentId, Integer refundAmount, String refundReason) throws ApiException {
        Refund refund = new Refund();
        refund.setAmount(new MoneyAmount().amount(refundAmount).currency(MoneyAmount.CurrencyEnum.JPY));
        refund.setMerchantRefundId(merchantRefundId);
        refund.setPaymentId(paypayPaymentId);
        refund.setReason(refundReason);
        refund.setRequestedAt(LocalDateTime.now().toEpochSecond(ZoneOffset.UTC));
        PaymentApi apiInstance = new PaymentApi(apiClient);
        RefundDetails response = apiInstance.refundPayment(refund);
        Refund responseData = new Refund();
        if (PAY_SUCCESS.equals(response.getResultInfo().getCode())) {
            responseData = response.getData();
        } else {
            log.info("申请退款,Error code : {}", response.getResultInfo().getCode());
            throw new RuntimeException((response.getResultInfo().getMessage()));
        }
        return responseData;
    }

测试验证

    @Test
    public void refundTest() {
        String merchantRefundId = "PayOr1858850799262629888";
        String paypayPaymentId = "04577353467534417920";
        Integer refundAmount = 1;
        String refundReason = "测试退款";
        Refund refund = null;
        try {
            refund = refund(merchantRefundId, paypayPaymentId, refundAmount, refundReason);
        } catch (ApiException e) {
            throw new RuntimeException(e);
        }
        System.out.println("退款信息:"+refund);
    }

测试结果

退款信息:class Refund {
    class RefundState {
        status: CREATED
        acceptedAt: 1732027365
    }
    merchantRefundId: PayOr1858850799262629888
    paymentId: 04577353467534417920
    amount: class MoneyAmount {
        amount: 1
        currency: JPY
    }
    requestedAt: 1732056164
    reason: 测试退款
    assumeMerchant: 848069133234880512
}

5.获取退款状态

想要确认退款状态,需要调用退款详情接口。退款结果有三种:CREATED、REFUNDED、REFUND_FAILED

  /**
     * 获取退款状态
     *
     * @param merchantPaymentId 商户提供的唯一退款交易ID
     * @return 退款状态
     */
    public static String getRefundDetail(String merchantPaymentId) throws ApiException {
        // Calling the method to get Refund Details
        PaymentApi apiInstance = new PaymentApi(apiClient);
        RefundDetails response = apiInstance.getRefundDetails(merchantPaymentId);
        String refundStatus = "";
        if (PAY_SUCCESS.equals(response.getResultInfo().getCode())) {
            Refund responseData = response.getData();
            refundStatus = responseData.getStatus().getValue();
        } else {
            log.info("退款详情,Error code : {}", response.getResultInfo().getCode());
            throw new RuntimeException((response.getResultInfo().getMessage()));
        }
        return refundStatus;
    }

测试验证

   @Test
    public void getRefundDetailTest() {
        String merchantPaymentId = "";
        String refundStatus = "";
        try {
            refundStatus = getRefundDetail(merchantPaymentId);
        } catch (ApiException e) {
            throw new RuntimeException(e);
        }
        System.out.println(refundStatus);
    }

测试结果

API: GET https://stg-api.sandbox.paypay.ne.jp/v2/refunds/PayOr1858850799262629888
REFUNDED

在这里插入图片描述
如有错误,欢迎各位前来指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值