From 0ab3a0651f72f07fafb6a5c72ccb384cbe163fff Mon Sep 17 00:00:00 2001 From: Justin Case Date: Tue, 10 Jun 2025 21:42:41 +0200 Subject: [PATCH] Add order attributes to Payment API --- lib/mollie.rb | 1 + lib/mollie/payment.rb | 16 +++++ lib/mollie/payment/line.rb | 50 ++++++++++++++ lib/mollie/util.rb | 28 +++++++- test/mollie/payment_test.rb | 129 ++++++++++++++++++++++++++++++++++++ 5 files changed, 221 insertions(+), 3 deletions(-) create mode 100644 lib/mollie/payment/line.rb diff --git a/lib/mollie.rb b/lib/mollie.rb index 6e7bd03..c922127 100644 --- a/lib/mollie.rb +++ b/lib/mollie.rb @@ -44,6 +44,7 @@ module Mollie require 'mollie/order/shipment' require 'mollie/payment/capture' require 'mollie/payment/chargeback' +require 'mollie/payment/line' require 'mollie/payment/refund' require 'mollie/settlement/capture' require 'mollie/settlement/chargeback' diff --git a/lib/mollie/payment.rb b/lib/mollie/payment.rb index fda5a01..afa0c35 100644 --- a/lib/mollie/payment.rb +++ b/lib/mollie/payment.rb @@ -49,6 +49,10 @@ class Payment < Base :cancel_url, :webhook_url + attr_reader :lines, + :billing_address, + :shipping_address + alias links _links def open? @@ -185,6 +189,18 @@ def amount_refunded=(amount_refunded) @amount_refunded = Mollie::Amount.new(amount_refunded) end + def lines=(lines) + @lines = lines.map { |line| Payment::Line.new(line) } + end + + def billing_address=(address) + @billing_address = OpenStruct.new(address) if address.is_a?(Hash) + end + + def shipping_address=(address) + @shipping_address = OpenStruct.new(address) if address.is_a?(Hash) + end + def checkout_url Util.extract_url(links, 'checkout') end diff --git a/lib/mollie/payment/line.rb b/lib/mollie/payment/line.rb new file mode 100644 index 0000000..b70f31b --- /dev/null +++ b/lib/mollie/payment/line.rb @@ -0,0 +1,50 @@ +module Mollie + class Payment + class Line < Base + attr_accessor :type, + :description, + :quantity, + :quantity_unit, + :vat_rate, + :sku, + :categories, + :image_url, + :product_url + + attr_reader :unit_price, + :discount_amount, + :recurring, + :total_amount, + :vat_amount + + def discounted? + !@discount_amount.nil? + end + + def unit_price=(amount) + @unit_price = Mollie::Amount.new(amount) + end + + def discount_amount=(amount) + @discount_amount = Mollie::Amount.new(amount) + end + + def recurring=(object) + return if object.nil? + + @recurring = OpenStruct.new(object).tap do |r| + r.amount = Mollie::Amount.new(r.amount) if r.amount + r.start_date = Date.parse(r.start_date) if r.start_date + end + end + + def total_amount=(amount) + @total_amount = Mollie::Amount.new(amount) + end + + def vat_amount=(amount) + @vat_amount = Mollie::Amount.new(amount) + end + end + end +end diff --git a/lib/mollie/util.rb b/lib/mollie/util.rb index d67fdf2..9691671 100644 --- a/lib/mollie/util.rb +++ b/lib/mollie/util.rb @@ -14,9 +14,31 @@ def nested_underscore_keys(obj) end end - def camelize_keys(hash) - hash.each_with_object({}) do |(key, value), camelized| - camelized[camelize(key)] = value + CAMELIZE_NESTED = [ + :lines, + "lines", + :recurring, + "recurring", + :billing_address, + "billing_address", + :shipping_address, + "shipping_address" + ].freeze + + def camelize_keys(object) + case object + when Hash + object.each_with_object({}) do |(key, value), camelized| + camelized[camelize(key)] = if CAMELIZE_NESTED.include?(key) + camelize_keys(value) + else + value + end + end + when Array + object.map { |value| camelize_keys(value) } + else + object end end diff --git a/test/mollie/payment_test.rb b/test/mollie/payment_test.rb index bf5079f..a8d7614 100644 --- a/test/mollie/payment_test.rb +++ b/test/mollie/payment_test.rb @@ -124,6 +124,135 @@ def test_refunded? assert_false Payment.new([]).refunded? end + def test_payment_lines_attributes + attributes = { + resource: "payment", + id: "tr_7UhSN1zuXS", + lines: [{ + type: "physical", + description: "LEGO 4440 Forest Police Station", + quantity: 1, + quantity_unit: "pcs", + unit_price: { value: "89.00", currency: "EUR" }, + discount_amount: { value: "10.00", currency: "EUR" }, + recurring: { + description: "A description of the recurring item.", + interval: "3 months", + amount: { value: "29.00", currency: "EUR" }, + times: 4, + start_date: "2025-05-04" + }, + total_amount: { value: "95.59", currency: "EUR" }, + vat_rate: "21.00", + vat_amount: { value: "16.59", currency: "EUR" }, + sku: "SKU-12345", + categories: ["eco", "meal", "gift", "sport_culture"], + image_url: "https://example.org/lego-4440-forest-police-station.jpg", + product_url: "https://example.org/lego-4440-forest-police-station" + }] + } + + payment = Payment.new(attributes) + line = payment.lines.first + + assert_equal Payment::Line, line.class + assert_equal "physical", line.type + assert_equal "LEGO 4440 Forest Police Station", line.description + assert_equal 1, line.quantity + assert_equal "pcs", line.quantity_unit + assert_equal 89.00, line.unit_price.value + assert_equal "EUR", line.unit_price.currency + assert_equal 10.00, line.discount_amount.value + assert_equal "EUR", line.discount_amount.currency + assert_equal 95.59, line.total_amount.value + assert_equal "EUR", line.total_amount.currency + assert_equal "21.00", line.vat_rate + assert_equal 16.59, line.vat_amount.value + assert_equal "EUR", line.vat_amount.currency + assert_equal "SKU-12345", line.sku + assert_equal ["eco", "meal", "gift", "sport_culture"], line.categories + assert_equal "https://example.org/lego-4440-forest-police-station.jpg", line.image_url + assert_equal "https://example.org/lego-4440-forest-police-station", line.product_url + + # Recurring object + assert_equal "A description of the recurring item.", line.recurring.description + assert_equal "3 months", line.recurring.interval + assert_equal 29.00, line.recurring.amount.value + assert_equal "EUR", line.recurring.amount.currency + assert_equal 4, line.recurring.times + assert_equal Date.parse("2025-05-04"), line.recurring.start_date + end + + def test_billing_address + attributes = { + resource: "payment", + id: "tr_7UhSN1zuXS", + billing_address: { + title: "Dhr", + given_name: "Piet", + family_name: "Mondriaan", + organization_name: "Mollie B.V.", + street_and_number: "Keizersgracht 313", + street_additional: "apt", + postal_code: "1234AB", + email: "piet@mondriaan.com", + phone: "+31208202070", + city: "Amsterdam", + region: "Noord-Holland", + country: "NL" + } + } + + payment = Payment.new(attributes) + assert_equal "Dhr", payment.billing_address.title + assert_equal "Piet", payment.billing_address.given_name + assert_equal "Mondriaan", payment.billing_address.family_name + assert_equal "Mollie B.V.", payment.billing_address.organization_name + assert_equal "Keizersgracht 313", payment.billing_address.street_and_number + assert_equal "apt", payment.billing_address.street_additional + assert_equal "1234AB", payment.billing_address.postal_code + assert_equal "piet@mondriaan.com", payment.billing_address.email + assert_equal "+31208202070", payment.billing_address.phone + assert_equal "Amsterdam", payment.billing_address.city + assert_equal "Noord-Holland", payment.billing_address.region + assert_equal "NL", payment.billing_address.country + end + + def test_shipping_address + attributes = { + resource: "payment", + id: "tr_7UhSN1zuXS", + shipping_address: { + title: "Dhr", + given_name: "Piet", + family_name: "Mondriaan", + organization_name: "Mollie B.V.", + street_and_number: "Keizersgracht 313", + street_additional: "apt", + postal_code: "1234AB", + email: "piet@mondriaan.com", + phone: "+31208202070", + city: "Amsterdam", + region: "Noord-Holland", + country: "NL" + } + } + + payment = Payment.new(attributes) + assert_equal "Dhr", payment.shipping_address.title + assert_equal "Piet", payment.shipping_address.given_name + assert_equal "Mondriaan", payment.shipping_address.family_name + assert_equal "Mollie B.V.", payment.shipping_address.organization_name + assert_equal "Keizersgracht 313", payment.shipping_address.street_and_number + assert_equal "apt", payment.shipping_address.street_additional + assert_equal "1234AB", payment.shipping_address.postal_code + assert_equal "piet@mondriaan.com", payment.shipping_address.email + assert_equal "+31208202070", payment.shipping_address.phone + assert_equal "Amsterdam", payment.shipping_address.city + assert_equal "Noord-Holland", payment.shipping_address.region + assert_equal "NL", payment.shipping_address.country + end + def test_create_payment stub_request(:post, 'https://api.mollie.com/v2/payments') .with(body: %({"amount":{"value":1.95,"currency":"EUR"}}))