简单实现一个支付宝离线支付的场景

156 阅读3分钟

支付宝离线支付的设计需要考虑多种场景,例如用户网络异常、商家网络异常、支付数据一致性等问题。以下提供一个简单的案例。

场景案例

场景描述: 某线下商家支持支付宝离线支付,用户在手机无网络的情况下,通过生成离线支付二维码完成支付。商家设备会保存支付请求,待商家设备恢复网络后,将离线支付请求批量上传到支付宝进行处理。此设计需要保证支付数据的一致性、离线支付信息的安全性以及系统的高可用性。

功能设计

核心功能:

  1. 用户生成离线支付二维码,包含必要支付信息(金额、订单号等)。
  2. 商家扫描二维码,离线保存支付信息。
  3. 商家设备恢复网络后,将支付信息上传支付宝进行验证。
  4. 支付结果同步至用户。

系统架构

  1. 支付二维码生成模块:负责用户端生成支付二维码。
  2. 商家离线保存模块:负责商家端保存离线支付数据。
  3. 离线支付同步模块:负责商家恢复网络后,批量同步离线支付数据。

技术实现

以下基于Spring Boot实现。

依赖配置(pom.xml)

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>com.google.zxing</groupId>
        <artifactId>core</artifactId>
        <version>3.4.1</version>
    </dependency>
    <dependency>
        <groupId>com.google.zxing</groupId>
        <artifactId>javase</artifactId>
        <version>3.4.1</version>
    </dependency>
</dependencies>

数据库模型

@Entity
@Table(name = "offline_payment")
public class OfflinePayment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String orderId;

    @Column(nullable = false)
    private Double amount;

    @Column(nullable = false)
    private String qrCode;

    @Column(nullable = false)
    private boolean synced = false;

    // Getters and Setters
}

支付二维码生成服务

@Service
public class QRCodeService {

    public String generateQRCode(String data) throws Exception {
        int width = 300;
        int height = 300;
        Map<EncodeHintType, Object> hints = new HashMap<>();
        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");

        BitMatrix bitMatrix = new MultiFormatWriter().encode(data, BarcodeFormat.QR_CODE, width, height, hints);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        MatrixToImageWriter.writeToStream(bitMatrix, "PNG", outputStream);

        byte[] qrCodeBytes = outputStream.toByteArray();
        return Base64.getEncoder().encodeToString(qrCodeBytes);
    }
}

离线支付保存接口

@RestController
@RequestMapping("/api/payments")
public class OfflinePaymentController {

    @Autowired
    private OfflinePaymentRepository offlinePaymentRepository;

    @PostMapping("/save")
    public ResponseEntity<String> savePayment(@RequestBody OfflinePayment payment) {
        payment.setSynced(false);
        offlinePaymentRepository.save(payment);
        return ResponseEntity.ok("Payment saved successfully");
    }

    @GetMapping("/unsynced")
    public List<OfflinePayment> getUnsyncedPayments() {
        return offlinePaymentRepository.findBySyncedFalse();
    }
}

离线支付同步任务

@Component
public class OfflinePaymentSyncTask {

    @Autowired
    private OfflinePaymentRepository offlinePaymentRepository;

    @Scheduled(fixedRate = 60000) // 每分钟执行一次
    public void syncPayments() {
        List<OfflinePayment> unsyncedPayments = offlinePaymentRepository.findBySyncedFalse();

        for (OfflinePayment payment : unsyncedPayments) {
            // 模拟向支付宝同步支付信息
            boolean success = syncWithAlipay(payment);
            if (success) {
                payment.setSynced(true);
                offlinePaymentRepository.save(payment);
            }
        }
    }

    private boolean syncWithAlipay(OfflinePayment payment) {
        // 模拟调用支付宝API
        System.out.println("Syncing payment: " + payment.getOrderId());
        return true;
    }
}

数据访问层

public interface OfflinePaymentRepository extends JpaRepository<OfflinePayment, Long> {
    List<OfflinePayment> findBySyncedFalse();
}

应用配置(application.properties)

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=update
spring.h2.console.enabled=true

测试流程

  1. 生成支付二维码

    • 调用QRCodeServicegenerateQRCode方法生成支付二维码。
    • 示例数据:{ "orderId": "123456", "amount": 100.0 }
  2. 保存离线支付数据

    • 调用/api/payments/save接口保存支付信息。
  3. 模拟离线状态后同步

    • 定时任务OfflinePaymentSyncTask会在设备网络恢复时同步未同步的支付信息。

该设计实现了支付宝离线支付的核心功能,考虑了离线场景的数据存储和同步问题。系统可以进一步扩展,例如增加支付验证、用户通知等功能。